import concur = module("concur");

var box:HTMLElement[];
var zone: number[];
var zsize: number[];
var width: number = 100;
var height: number = 100;
var cellSize: number = 10;
var pad: number = 10;

var wallStyle = "1px solid black";

function init():concur.Result {
    var frame = concur.newFrame("build");
    try {
        var i: number;
        var j: number;
        var size = width * height;
        box = <HTMLElement[]>Array(height * width);
        zone = <number[]>Array(height * width);
        zsize = <number[]>Array(height * width);
        var body = document.body;
        body.innerHTML = "";
        var index = 0;
        frame.loop((): concur.Result => {
            while (index < size) {
                i = Math.floor(index / height);
                j = index % height;
                var elem = <HTMLElement>document.createElement("div");
                elem.style.position = "absolute";
                if (i == 0)
                    elem.style.borderLeft = wallStyle;
                if (j == 0)
                    elem.style.borderTop = wallStyle;
                elem.style.borderBottom = wallStyle;
                elem.style.borderRight = wallStyle;
                body.appendChild(elem);
                box[index] = elem;
                zone[index] = i * height + j;
                zsize[index] = 1;
                index++;
                var f = frame.timeSlice();
                if (f.isPending())
                    return f;
            }
            return null;
        }).then(() =>{
            frame.finish();
        });
    } catch (err) {
        frame.raise(err);
    }
    return frame.result();
}

function resize(): concur.Result {
    try {
        var frame = concur.newFrame("resize");
        var i: number;
        var j: number;
        var size = width * height;
        pad = 10;
        cellSize = Math.floor((window.innerWidth - 2 * pad) / width);
        pad = Math.ceil((window.innerWidth - cellSize * width) / 2);
        var index = 0;
        var dim = cellSize + "px";
        frame.loop((): concur.Result => {
            while (index < size) {
                i = Math.floor(index / height);
                j = index % height;
                var elem = box[index];
                var x = pad + i * cellSize;
                var y = pad + j * cellSize;
                elem.style.position = "absolute";
                elem.style.left = x + "px";
                elem.style.top = y + "px";
                if (i == 0) {
                    elem.style.width = (cellSize - 1) + "px";
                } else {
                    elem.style.width = dim;
                }
                if (j == 0) {
                    elem.style.height = (cellSize - 1) + "px";
                } else {
                    elem.style.height = dim;
                }
                index++;
                var f = frame.timeSlice();
                if (f.isPending())
                    return f;
            }
            return null;
        }).then(() =>{
            frame.finish();
        });
    } catch (err) {
        frame.raise(err);
    }
    return frame.result();
}

function spawnWalkers(clickEventSource: concur.EventSource): concur.Result {
    var frame = concur.newFrame("spawnWalkers");
    var walkerCount = 0;
    frame.loop((): concur.Result => {
        return clickEventSource.nextEvent().thenAsync((evt: MouseEvent): concur.Result => {
            var frame = concur.newFrame("spawnWalkers.internal");
            var colors = ["red", "green", "blue", "cyan", "magenta", "yellow"];
            var color = colors[Math.floor(Math.random() * colors.length)];
            frame.getScheduler().run((): concur.Result => {
                var frame = concur.newFrame("walker");
                var i = Math.max(0, Math.min(width-1, Math.floor((evt.clientX - pad) / cellSize)));
                var j = Math.max(0, Math.min(height-1, Math.floor((evt.clientY - pad) / cellSize)));
                var walkerElem = <HTMLElement>document.createElement("div");
                walkerElem.style.position = "absolute";
                walkerElem.style.width = (cellSize-1) + "px";
                walkerElem.style.height = (cellSize-1) + "px";
                walkerElem.style.top = (pad + j*cellSize + 1) + "px";
                walkerElem.style.left = (pad + i*cellSize + 1) + "px";
                walkerElem.style.background = color;
                document.body.appendChild(walkerElem);
                frame.loop((): concur.Result => {
                    return frame.sleep(100).thenAsync((): concur.Result =>{
                        var frame = concur.newFrame("walker.step");
                        var i1 = i;
                        var j1 = j;
                        var pname: string;
                        var newpos = false;
                        switch (Math.floor(Math.random() * 4)) {
                        case 0:
                            i1--;
                            pname = "borderRight";
                            newpos = true;
                            break;
                        case 1:
                            i1++;
                            pname = "borderRight";
                            break;
                        case 2:
                            j1--;
                            pname = "borderBottom";
                            newpos = true;
                            break;
                        case 3:
                            j1++;
                            pname = "borderBottom";
                            break;
                        }
                        if (0 <= i1 && i1 < width && 0 <= j1 && j1 < height) {
                            var elem = (newpos ? box[i1 * height + j1] : box[i * height + j]);
                            if (elem.style[pname] != wallStyle) {
                                i = i1;
                                j = j1;
                                window.console.log("moved to " + i + " " + j);
                                walkerElem.style.top = (pad + j*cellSize + 1) + "px";
                                walkerElem.style.left = (pad + i*cellSize + 1) + "px";
                            }
                        }
                        walkerElem.style.width = (cellSize-1) + "px";
                        walkerElem.style.height = (cellSize-1) + "px";
                        frame.finish();
                        return frame.result();
                    });
                });
                return frame.result();
            }, "walker" + (walkerCount++));
            frame.finish();
            return frame.result();
        });
    });
    return frame.result();
}

function findSet( z:number ):number {
    while( zone[z] != z )
        z = zone[z];
    return z;
}

function merge( s1:number, s2:number ):void {
    if (zsize[s1] > zsize[s2]) {
        zone[s2] = s1;
        zsize[s1] += zsize[s2];
    } else {
        zone[s1] = s2;
        zsize[s2] += zsize[s1];
    }
}

class Wall {
    constructor (public isvert: bool, public i: number, public j: number) {
    }
}

function pickRandomWall(): concur.Result {
    var frame = concur.newFrame("pickAWall");
    frame.timeSlice().then(() =>{
        var isvert: bool = Math.random() > 0.5;
        var i: number, j: number;
        if (isvert) {
            i = 1 + Math.floor(Math.random() * (width - 1)); // 1:(width-1)
            j = Math.floor(Math.random() * height); // 0:(height-1)
        } else {
            i = Math.floor(Math.random() * width); // 0:(width-1)
            j = 1 + Math.floor(Math.random() * (height - 1)); // 1:(height-1)
        }
        frame.finish(new Wall(isvert, i, j));
    });
    return frame.result();
}

function pickGoodWall(): concur.Result {
    var frame = concur.newFrame("pickGoodWall");
    try {
        var zone1: number = 0;
        var zone2: number = 0;
        var wall0: Wall;
        frame.loop((): concur.Result =>{
            while (zone1 == zone2) {
                var frameInternal = concur.newFrame("pickGoodWall.internal");
                pickRandomWall().then((wall: Wall) =>{
                    wall0 = wall;
                    if (wall.isvert) {
                        zone1 = (wall.i - 1) * height + wall.j;
                        zone2 = wall.i * height + wall.j;
                    } else {
                        zone1 = wall.i * height + wall.j - 1;
                        zone2 = wall.i * height + wall.j;
                    }
                    zone1 = findSet(zone1);
                    zone2 = findSet(zone2);
                    frameInternal.finish();
                });
                var resultInternal = frameInternal.result();
                if (resultInternal.isPending())
                    return resultInternal;
            }
            return null;
        }).then(() =>{
            merge(zone1, zone2);
            frame.finish(wall0);
        });
    } catch (err) {
        frame.raise(err);
    }
    return frame.result();
}

function build():concur.Result {
    var frame = concur.newFrame("build");
    try {
        var niter = height * width - 1;
        frame.loop(function (): concur.Result {
            while (niter > 0) {
                var frameInternal = concur.newFrame("build.internal");
                pickGoodWall().then((wall: Wall) =>{
                    niter--;
                    var elem: HTMLElement;
                    if (wall.isvert) {
                        elem = box[(wall.i - 1) * height + wall.j];
                        elem.style.borderRight = "none";
                        elem.style.width = (parseInt(elem.style.width) + 1) + "px";
                    } else {
                        elem = box[wall.i * height + wall.j - 1];
                        elem.style.borderBottom = "none";
                        elem.style.height = (parseInt(elem.style.height) + 1) + "px";
                    }
                    frameInternal.finish();
                });
                var resultInternal = frameInternal.result();
                if (resultInternal.isPending())
                    return resultInternal;
            }
            return null;
        }).then(function (): void {
            frame.finish();
        });
    } catch (err) {
        frame.raise(err);
    }
    return frame.result();
}

function handleResize(resizeEventSource: concur.EventSource): concur.Result {
    var frame = concur.newFrame("handleResize");
    frame.loop( ():concur.Result =>resizeEventSource.nextEvent().thenAsync(resize) );
    return frame.result();
}

function main() {
    var scheduler = concur.newScheduler();

    var resizeEventSource = concur.newEventSource();
    resizeEventSource.attach(window, "resize");

    var clickEventSource = concur.newEventSource();
    clickEventSource.attach(document.body, "click");

    scheduler.run(() => init().thenAsync(resize).thenAsync(() =>{
        scheduler.run(() =>handleResize(resizeEventSource));
        scheduler.run(() =>spawnWalkers(clickEventSource));
        return build();
    }));
}

if(window["__loaded"])
    main();
else
    window.onload = main;